package com.zjpavt.client.hal;

import com.zjpavt.client.Connection;
import com.zjpavt.client.ConnectionService;
import com.zjpavt.client.enums.KeyCodeEnum;
import com.zjpavt.client.event.EventRegisterCenter;
import com.zjpavt.client.page.PageManager;
import com.zjpavt.client.udp.UdpServer;
import com.zjpavt.client.util.ClientConfig;
import com.zjpavt.client.util.FileUtil;
import com.zjpavt.client.util.LoggerUtil;
import io.netty.util.concurrent.DefaultThreadFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.*;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

/**
 * 键盘按键监听类
 *
 * @author zjpavt
 */
public class KeyPad implements UdpServer.UdpDispatch {

    private static Logger log = LoggerFactory.getLogger(KeyPad.class);
    /**
     * 当有按键出现时触发延时发送任务，每隔若干秒向服务器发送一次数据，数据长度的取决于MAX_SEND_KEY_CODE_LENGTH
     */
    private static final int DEFAULT_SCHEDULE_SEND_KEY_CODE_TIME = 5;
    /**
      * 单次从内存中取出多长的数据发送到服务器
     */
    private static final int MAX_SEND_KEY_CODE_LENGTH = 50;
    /**
     * 在内存缓存的最长按键键值长度（当没有连接到服务器时，存在StringBuilder中）
     */
    private static final int MAX_STORE_SEND_KEY_CODE_LENGTH = 100;
    /**
     * 每次从文件读取按键到内存的长度
     */
    private static final int PIECE_STORE_SEND_KEY_CODE_LENGTH = 500;
    /**
     * 重新连接到服务器后 延时多久向服务器发送缓存的按键 ms
     */
    private static final int SEND_STORE_KEY_CODE_DELAY_TIME = 10 * 1000;
    /**
     * 重新连接到服务器后 每次向服务器发送缓存的按键的周期 ms
     */
    private static final int SEND_STORE_KEY_CODE_PERIOD = 500;
    private static final String COMMAND_END = ">";
    private static final KeyPad INSTANCE = new KeyPad();


    private KeyPad() {
    }

    public static KeyPad getInstance() {
        return INSTANCE;
    }

    private final List<KeyListener> listenerList = new ArrayList<>();

    private final KeyPadSimulator keyPadSimulator = new KeyPadSimulator();

    public void addKeyListener(KeyListener listener) {
        synchronized (listenerList) {
            listenerList.add(listener);
            keyPadSimulator.addKeyListener(listener);
        }
    }

    public String simulatorKeyPress(String keyCode) {
        this.keyPadSimulator.notifyChanged(keyCode);
        return "";
    }

    @Override
    public void notifyChanged(String keyValue) {
        log.info("key " + keyValue + " pressed");
//		屏幕重启后需要刷新页面的内容
        if (keyValue.equals("0x1")) {
            KeypadLedController.changeLedStatusAllByCommand();
            PageManager.getInstance().setPage(PageManager.getInstance().getCurrPageNum());
        }
        KeyCodeEnum keyCodeEnum = KeyCodeEnum.getKeyCode(keyValue);
        if (keyCodeEnum == null) {
            return;
        }
        Map<String, Object> map = new HashMap<>(1);
        map.put(EventRegisterCenter.KEY_NAME, keyCodeEnum.getKeyValue());
        EventRegisterCenter.getInstance().triggerRegisterTask(EventRegisterCenter.KEY_PRESS,map);
        if (keyCodeEnum == null) {
            log.error("NO know key value: " + keyValue);
            return;
        }
        List<KeyListener> copyList;
        synchronized (listenerList) {
            copyList = new ArrayList<>(listenerList);
        }
        for (KeyListener listener : copyList) {
            listener.onKeyPressed(keyCodeEnum);
        }
//		按键推送暂时不使用
//		sendAndSaveKeyCode(keyCodeEnum);
    }

    /**
     * 增加一个按键时锁定 发送按键时锁定  确切的是修改tempKeyCodeStringBuffer
     * */
    ReentrantLock keyCodeLock = new ReentrantLock();
    /**
     * 读取 或 保存文件时的锁
     */
    ReentrantLock keyCodeFileLock = new ReentrantLock();
    StringBuffer tempKeyCodeStringBuffer = new StringBuffer();
    boolean isReady2Send = false;
    /**
     * 设备连接监听器，当设备连接时将本地缓存的数据瑞松到服务器
     */
    private ConnectionService.ChannelListener channelListener = new ConnectionService.ChannelListener() {
        @Override
        public void onActive(boolean isActive) {
            if (isActive) {
                File keyCodeFile = new File(ClientConfig.STORE_KEY_CODE);
                if (keyCodeFile.exists()) {
                    try {
                        final InputStream inputStream = new FileInputStream(keyCodeFile);
                        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("keyCodeInFile"));
                        delayReadKeyCodeInFile(inputStream, scheduledExecutorService, SEND_STORE_KEY_CODE_DELAY_TIME, new StringBuilder());
                    } catch (FileNotFoundException e) {
                        log.info(keyCodeFile.getAbsolutePath() + "is exist but can't read!");
                    }
                }
                delaySendKeyCode(Connection.getInstance());
                ConnectionService.getInstance().removeListener(this);
            }
        }

        @Override
        public void onReceiveData() {

        }

        /**
         	延时从文件中读取  todo 要不要考虑恢复连接以后又断了 或者时断时续？？？ 稳定连接一分钟后才开始读取离线的数据。
         * @param inputStream
         * @param scheduledExecutorService
         * @param delayTime
         * @param fileKeyCodeStringBuilder
         */
        private void delayReadKeyCodeInFile(InputStream inputStream, ScheduledExecutorService scheduledExecutorService, int delayTime, StringBuilder fileKeyCodeStringBuilder) {
            log.trace("delayReadKeyCodeInFile begin:" + System.currentTimeMillis() + "" + delayTime);
            scheduledExecutorService.schedule(() -> {
                log.trace("delayReadKeyCodeInFile run:" + System.currentTimeMillis() + "" + delayTime);
                if (!Connection.getInstance().isActive()) {
                    return;
                }
                final byte[] keyCodeTempBytes = new byte[PIECE_STORE_SEND_KEY_CODE_LENGTH];
                try {
                    //开始读取文件时把文件锁定  todo 只是做到了每个文件单次读取时的锁定 没有做到全文件锁定 因为文件做了周期读取
                    keyCodeFileLock.lock();
                    if (inputStream.read(keyCodeTempBytes) != -1) {
                        delayReadKeyCodeInFile(inputStream, scheduledExecutorService, SEND_STORE_KEY_CODE_PERIOD, fileKeyCodeStringBuilder);
                        fileKeyCodeStringBuilder.append(new String(keyCodeTempBytes, ClientConfig.DEFAULT_ENCODING));
                        int index = fileKeyCodeStringBuilder.lastIndexOf(COMMAND_END);
                        String getKeyCodeString = fileKeyCodeStringBuilder.substring(0, index + 1);
                        log.debug("getKeyCodeString: " + getKeyCodeString);
                        tempKeyCodeStringBuffer.append(getKeyCodeString);
                        delaySendKeyCode(Connection.getInstance());
                        fileKeyCodeStringBuilder.delete(0, index + 1);
                    } else {
                        if (fileKeyCodeStringBuilder.length() != 0) {
                            log.error("the fileKeyCodeStringBuilder should be 0 but be" + fileKeyCodeStringBuilder.length());
                        }
                        inputStream.close();
                        File file = new File(ClientConfig.STORE_KEY_CODE);
                        if (!file.delete()) {
                            log.error("delete the key code file fail!");
                        }
                    }
                } catch (UnsupportedEncodingException e) {
                    log.error(LoggerUtil.convertException2String(e));
                } catch (IOException e) {
                    log.error(LoggerUtil.convertException2String(e));
                }
                finally {
                    keyCodeFileLock.unlock();
                }

            }, delayTime, TimeUnit.MILLISECONDS);
        }
    };

    /**
     * 发送按键到服务器
     * @param keyCodeEnum 按键的值
     */
    private void sendAndSaveKeyCode(KeyCodeEnum keyCodeEnum) {
        log.debug(keyCodeEnum.getDescription());
        Connection connection = Connection.getInstance();
        keyCodeLock.lock();
        try {
            //按键时间设置为当前时间
            tempKeyCodeStringBuffer.append(keyCodeEnum.getDescription2Server());
            //仅当 没有启动延时发送程序 且 通道处于活跃状态
            if (!isReady2Send && connection.isActive()) {
                delaySendKeyCode(connection);
            } else if(!connection.isActive()) {
                if (tempKeyCodeStringBuffer.length() > MAX_STORE_SEND_KEY_CODE_LENGTH) {
                    appendKeyCode2LocalFile();
                }
                ConnectionService.getInstance().addListener(channelListener);
            }
            log.debug(tempKeyCodeStringBuffer.toString());
        }
        finally {
            keyCodeLock.unlock();
        }
    }

    /**
     * 保存keyCode数据到文件 当且仅当通道被关闭时
     */
    private void appendKeyCode2LocalFile() {
        log.trace("appendKeyCode2LocalFile");
        try {
            keyCodeFileLock.lock();
            File keyCodeFile = new File(ClientConfig.STORE_KEY_CODE);
            FileUtil.makeSureFileExist(keyCodeFile);
            try {
                //单次写入锁。
                keyCodeFileLock.lock();
                OutputStream storeOutputStream = new FileOutputStream(keyCodeFile, true);
                storeOutputStream.write(tempKeyCodeStringBuffer.toString().getBytes(ClientConfig.DEFAULT_ENCODING));
                keyCodeFileLock.unlock();
                log.trace("appendKeyCode2LocalFile stored:" + tempKeyCodeStringBuffer.toString());
                tempKeyCodeStringBuffer.delete(0, tempKeyCodeStringBuffer.length());
            } catch (FileNotFoundException e) {
                log.error("cannot be check file now!");
            } catch (IOException ioe) {
                log.error("get byte by UTF-8 error");
            }
        }
        finally {
            keyCodeFileLock.unlock();
        }
    }



    /**
     * 延迟发送
     * @param connection 单例的connection
     */
    private void delaySendKeyCode(Connection connection) {
        isReady2Send = true;
        ScheduledExecutorService scheduledExecutorService = new ScheduledThreadPoolExecutor(1, new DefaultThreadFactory("sendKeyCode"));
        scheduledExecutorService.schedule(() -> {
            keyCodeLock.lock();
            try {
                log.debug("delaySendKeyCode: " + tempKeyCodeStringBuffer.toString() + " channel active:" + connection.isActive());
                //如果等到发送的时间发现 通道被关闭了 继续存储按键 不发送
                if (connection.isActive()) {
                    //数据量在50条以上
                    while (tempKeyCodeStringBuffer.length() > MAX_SEND_KEY_CODE_LENGTH) {
                        // index is begin at 0.
                        int endIndex = tempKeyCodeStringBuffer.indexOf(COMMAND_END, MAX_SEND_KEY_CODE_LENGTH) + 1;
                        String msg2Send = (String) tempKeyCodeStringBuffer.subSequence(0, endIndex);
                        connection.appendMsgToQueue(msg2Send);
                        tempKeyCodeStringBuffer.delete(0, endIndex);
                    }
                    if (tempKeyCodeStringBuffer.length() != 0) {
                        connection.appendMsgToQueue(tempKeyCodeStringBuffer.toString());
                        tempKeyCodeStringBuffer.setLength(0);
                    }
                    //延迟发送的时候 通道被关闭了！
                } else {
                    ConnectionService.getInstance().addListener(channelListener);
                }
                isReady2Send = false;
            }
            finally {
                keyCodeLock.unlock();
            }
        }, DEFAULT_SCHEDULE_SEND_KEY_CODE_TIME, TimeUnit.SECONDS);
    }

    public void removeKeyListener(KeyListener listener) {
        synchronized (listenerList) {
            listenerList.remove(listener);
        }
    }

    public void removeAllListeners() {
        synchronized (listenerList) {
            if (!listenerList.isEmpty()) {
                listenerList.clear();
            }
        }
    }

    public interface KeyListener {
        /**
         * 按键监听处理
         *
         * @param keyCodeEnum
         */
        void onKeyPressed(KeyCodeEnum keyCodeEnum);
    }
}
